package com.hhf.feioj.judge.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.hhf.feioj.common.ErrorCode;
import com.hhf.feioj.exception.BusinessException;
import com.hhf.feioj.judge.codesandbox.CodeSandbox;
import com.hhf.feioj.judge.codesandbox.factory.CodeSandboxProxyFactory;
import com.hhf.feioj.judge.codesandbox.model.ExecuteCodeRequest;
import com.hhf.feioj.judge.codesandbox.model.ExecuteCodeResponse;
import com.hhf.feioj.judge.service.JudgeService;
import com.hhf.feioj.judge.strategy.JudgeStrategyContext;

import com.hhf.feioj.model.dto.question.JudgeCase;
import com.hhf.feioj.model.dto.question.JudgeConfig;
import com.hhf.feioj.model.dto.question.JudgeInfo;
import com.hhf.feioj.model.entity.Question;
import com.hhf.feioj.model.entity.QuestionSubmit;
import com.hhf.feioj.model.enums.JudgeInfoMessageEnum;
import com.hhf.feioj.model.enums.QuestionSubmitStatusEnum;
import com.hhf.feioj.service.QuestionService;
import com.hhf.feioj.service.QuestionSubmitService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author hhf
 * @Date 2024/10/28
 * @Description
 */
@Service
public class JudgeServiceImpl implements JudgeService {

    @Resource
    private QuestionService questionService;

    @Resource
    @Lazy
    private QuestionSubmitService questionSubmitService;

    @Override
    public void doJudge(long questionSubmitId) {
        //1. 判题服务接受questionSubmitId,查询提交问题的信息,对信息进行判断
        QuestionSubmit dbQuestionSubmit = questionSubmitService.getById(questionSubmitId);
        //1.1 判断问题提交信息是否存在
        if (dbQuestionSubmit == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "提交问题不存在");
        }
        //1.2 判断问题是否已经提交
        Integer status = dbQuestionSubmit.getStatus();
        if (status.equals(QuestionSubmitStatusEnum.RUNNING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "问题正在判题中,请不要重复提交");
        }

        //2. 获取问题的信息,对问题信息进行判断
        Long questionId = dbQuestionSubmit.getQuestionId();
        Question dbQuestion = questionService.getById(questionId);
        //2.1 判断问题信息是否存在
        if (dbQuestion == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "问题不存在");
        }

        //3. 修改问题提交的状态为正在判题(RUNNING),防止重复提交问题
        QuestionSubmit updateQuestionSubmit = new QuestionSubmit();
        updateQuestionSubmit.setId(questionSubmitId);
        updateQuestionSubmit.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        boolean update = questionSubmitService.updateById(updateQuestionSubmit);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "问题提交状态更新错误");
        }

        //4. 调用代码沙箱,获取代码执行信息
        //将判题输入用例及用户提交的代码信息(QuestionSubmitAddRequest)发送给代码沙箱进行运行
        CodeSandbox codeSandbox = CodeSandboxProxyFactory.getInstance();
        //4.1 获取 ExecuteCodeRequest 所需要的信息
        String language = dbQuestionSubmit.getLanguage();
        String code = dbQuestionSubmit.getCode();
        //获取输入用例
        String judgeCaseStr = dbQuestion.getJudgeCase();
        List<JudgeCase> judgeCaseList = JSONUtil.toList(judgeCaseStr, JudgeCase.class);
        List<String> inputList = judgeCaseList.stream().map(JudgeCase::getInput).collect(Collectors.toList());
        List<String> outputList = judgeCaseList.stream().map(JudgeCase::getOutput).collect(Collectors.toList());
        //4.2 构造 ExecuteCodeRequest
        ExecuteCodeRequest request = ExecuteCodeRequest.builder()
                .code(code)
                .inputList(inputList)
                .language(language)
                .build();
        ExecuteCodeResponse response = codeSandbox.executeCode(request);

        //5. 获取代码沙箱返回的结果
        List<String> runOutputList = response.getOutputList();
        JudgeInfo runJudgeInfo = response.getJudgeInfo();


        //构造需要的JudgeInfo
        String judgeConfigStr = dbQuestion.getJudgeConfig();
        JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);
        JudgeInfo needJudgeInfo = new JudgeInfo();
        needJudgeInfo.setMemory(judgeConfig.getMemoryLimit());
        needJudgeInfo.setTime(judgeConfig.getTimeLimit());



        //6. 根据不同的编程语言,获取JudgeInfo的信息(比较needJudgeInfo与runJudgeInfo)
        //使用策略模式,对不同的编程语言执行不同的策略,减少if语句的产生
        //比如java语言, timeLimit的限制 + 10000L
        JudgeStrategyContext context = new JudgeStrategyContext(language,runJudgeInfo,needJudgeInfo);
        JudgeInfo judgeInfo = context.executeStrategy();


        //7. 判断提交问题的答案是否正确
        //7.1 判断沙箱执行的结果输出数量是否和预期输出数量相等
        if (inputList.size() != outputList.size()){
            judgeInfo.setMessage(JudgeInfoMessageEnum.WRONG_ANSWER.getValue());
        }
        //7.2 依次判断每一项输出和预期输出是否相等
        for (int i = 0; i < runOutputList.size(); i++) {
            if (!runOutputList.get(i).equals(outputList.get(i))){
                judgeInfo.setMessage(JudgeInfoMessageEnum.WRONG_ANSWER.getValue());
                break;
            }
        }

        //8. 修改数据库中的判题结果
        updateQuestionSubmit = new QuestionSubmit();
        updateQuestionSubmit.setId(questionSubmitId);
        updateQuestionSubmit.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        updateQuestionSubmit.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        update = questionSubmitService.updateById(updateQuestionSubmit);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "问题提交状态更新错误");
        }
    }
}
